/*
 * Copyright 2010 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.anim.impl
{
	import flash.utils.Dictionary;
	import sweezy.anim.core.AnimationEvent;
	import sweezy.anim.core.IAnimation;
	import sweezy.anim.core.IEasing;
	import sweezy.anim.core.IPhisycalEasing;
	import sweezy.anim.core.IPhisycalEasingSupportUpdater;
	import sweezy.anim.core.IPropertyAnimation;
	import sweezy.anim.core.IUpdater;
	import sweezy.anim.easing.Cubic;
	import sweezy.anim.easing.Linear;
	import sweezy.anim.easing.Quadratic;
	import sweezy.anim.easing.Quartic;
	import sweezy.anim.easing.Quintic;
	import sweezy.anim.updater.UpdaterProperty;
	import sweezy.anim.updater.core.DefaultUpdater;

	internal class PropertyAnimation extends AnimationBase implements IPropertyAnimation
	{

		private static var _defaultUpdater:IUpdater;

		private static var _inlineEasingTable:Dictionary;

		private static function init():void
		{
			_inlineEasingTable = new Dictionary();
			_inlineEasingTable[Linear.easeIn] = 0x000000;
			_inlineEasingTable[Linear.easeOut] = 0x000000;
			_inlineEasingTable[Linear.easeInOut] = 0x000000;
			_inlineEasingTable[Linear.easeNone] = 0x000000;
			_inlineEasingTable[Quadratic.easeIn] = 0x220000;
			_inlineEasingTable[Quadratic.easeOut] = 0x320000;
			_inlineEasingTable[Quadratic.easeInOut] = 0x420000;
			_inlineEasingTable[Cubic.easeIn] = 0x230000;
			_inlineEasingTable[Cubic.easeOut] = 0x330000;
			_inlineEasingTable[Cubic.easeInOut] = 0x430000;
			_inlineEasingTable[Quartic.easeIn] = 0x240000;
			_inlineEasingTable[Quartic.easeOut] = 0x340000;
			_inlineEasingTable[Quartic.easeInOut] = 0x440000;
			_inlineEasingTable[Quintic.easeIn] = 0x250000;
			_inlineEasingTable[Quintic.easeOut] = 0x350000;
			_inlineEasingTable[Quintic.easeInOut] = 0x450000;

			_defaultUpdater = new DefaultUpdater();
		}

		internal var _element:Object;

		private var _delay:uint;

		private var _easing:IEasing;

		private var _option:Object;

		private var _updaterHolder:UpdaterHolder;

		public function PropertyAnimation(manager:AnimationManager, element:Object, duration:uint, easing:IEasing, delay:uint)
		{
			super(manager);

			_element = element;
			_duration = duration;
			_delay = delay;

			if (easing === null)
			{
				easing = manager.defaultEasing;
			}
			if (easing in _inlineEasingTable)
			{
				_state |= _inlineEasingTable[easing];
			}
			else
			{
				_state |= 0x100000;
				_easing = easing;
			}

			var updater:IUpdater = _manager.getUpdaterByElement(element);
			if (updater === null)
			{
				updater = _defaultUpdater;
			}
			_updaterHolder = new UpdaterHolder(updater, null);
		}

		public function by(name:String, offset:Number, round:Boolean = false):IPropertyAnimation
		{
			return registerProperty(name, NaN, offset, true, round);
		}

		public function get delay():uint
		{
			return _delay;
		}

		override public function get duration():uint
		{
			if ((_state & 0x08) === 0x00)
			{
				initialize();
			}

			return _duration;
		}

		public function fromBy(name:String, start:Number, offset:Number, round:Boolean = false):IPropertyAnimation
		{
			return registerProperty(name, start, offset, true, round);
		}

		public function fromTo(name:String, start:Number, end:Number, round:Boolean = false):IPropertyAnimation
		{
			return registerProperty(name, start, end, false, round);
		}

		override public function initialize():void
		{
			_state |= 0x08;

			var holder:UpdaterHolder;
			if ((_state & 0x03000000) !== 0x00000000)
			{
				initialize0();
			}
			else
			{
				for (holder = _updaterHolder; holder !== null; holder = holder.next)
				{
					holder.updater.initialize(_element, holder.property, holder.prefix, _option, this);
				}
			}

			if (_easing is IPhisycalEasing)
			{
				var duration:uint;
				var maxDuration:uint;
				var property:UpdaterProperty;

				for (holder = _updaterHolder; holder !== null; holder = holder.next)
				{
					if (holder.updater is IPhisycalEasingSupportUpdater)
					{
						duration = (holder.updater as IPhisycalEasingSupportUpdater).getMaxDuration(_element, holder.property, holder.prefix, _option, this);
						maxDuration = maxDuration < duration ? duration : maxDuration;
					}
					else
					{
						property = holder.property;
						while (property !== null)
						{
							duration = (_easing as IPhisycalEasing).computeDuration(property.end - property.start);
							maxDuration = maxDuration < duration ? duration : maxDuration;
							property = property.next;
						}
					}
				}

				_duration = maxDuration;
			}

			_duration = _duration === 0 ? _manager.defaultDuration : _duration;
			var d:Number = _delay + _duration;
			_duration = int.MAX_VALUE < d ? int.MAX_VALUE : uint(d);
		}

		public function get onError():Function
		{
			return _events === null ? null : _events.onError;
		}

		public function set onError(value:Function):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onError = value;
			if (value !== null)
			{
				_state |= 0x02000000;
			}
			else
			{
				_state &= ~0x02000000;
			}
		}

		public function get onErrorScope():Object
		{
			return _events === null ? null : _events.onErrorScope;
		}

		public function set onErrorScope(value:Object):void
		{
			if (_events === null && value === null)
			{
				return;
			}

			events.onErrorScope = value === null ? this : value;
		}

		public function get option():Object
		{
			return _option;
		}

		public function set option(value:Object):void
		{
			_option = value;
		}

		override public function pause():IAnimation
		{
			if ((_state & 0x01) === 0x00)
			{
				return this;
			}

			if (_managedName !== null)
			{
				delete activeAnimations[_managedName];
				_managedName = null;
			}
			_state &= ~0x01;
			_state |= 0x02;

			if ((_state & 0x4400) !== 0x0000)
			{
				(_state & 0x0400) === 0x0400 && _events.dispatchEvent(new AnimationEvent("animationPause"));
				(_state & 0x4000) === 0x4000 && _events.onPause.apply(_events.onPauseScope, _events.onPauseParams);
			}

			return this;
		}

		override public function set position(value:int):void
		{
			if ((_state & 0x08) === 0x00)
			{
				initialize();
			}

			var complete:int;
			if (value >= _duration)
			{
				if ((_state & 0x10) === 0x10)
				{
					_position = value - int(value / _duration) * _duration;
				}
				else
				{
					_position = _duration;
					complete = _state & 0x01;
				}
			}
			else if (value > -1)
			{
				_position = value;
			}
			else if (_position === -1)
			{
				return;
			}
			else
			{
				_position = -1;
			}

			var f:Number = _position <= _delay ? 0 : (_position - _delay) / (_duration - _delay);
			if (f > 0 || (f === 0 && (_state & 0x10000000) === 0x00000000))
			{
				f === 0 ? (_state |= 0x10000000) : (_state &= ~0x10000000);
				if ((_state & 0xff0000) !== 0x000000)
				{
					var p:uint;
					var t:uint = _state & 0xf00000;
					if (t === 0x100000)
					{
						f = _easing.ease(f);
					}
					else if (t === 0x200000)
					{
						p = _state & 0x0f0000;
						f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
					}
					else if (t === 0x300000)
					{
						p = _state & 0x0f0000;
						f = 1 - f;
						f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
						f = 1 - f;
					}
					else if (f < 0.5)
					{
						p = _state & 0x0f0000;
						f = f * 2;
						f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
						f = f * 0.5;
					}
					else
					{
						p = _state & 0x0f0000;
						f = f * -2 + 2;
						f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
						f = 1 - f * 0.5;
					}
				}

				if ((_state & 0x03000000) !== 0x00000000)
				{
					update0(f);
				}
				else
				{
					if (_updaterHolder.next === null)
					{
						_updaterHolder.updater.update(_element, _updaterHolder.property, _updaterHolder.prefix, f, _option, this);
					}
					else
					{
						for (var holder:UpdaterHolder = _updaterHolder; holder !== null; holder = holder.next)
						{
							holder.updater.update(_element, holder.property, holder.prefix, f, _option, this);
						}
					}
				}

				if ((_state & 0x1100) !== 0x0000)
				{
					(_state & 0x0100) === 0x0100 && _events.dispatchEvent(new AnimationEvent("animationUpdate"));
					(_state & 0x1000) === 0x1000 && _events.onUpdate.apply(_events.onUpdateScope, _events.onUpdateParams);
				}

				if (complete === 0x01)
				{
					_state &= ~0x01;
					_state |= 0x02;
					if (_managedName !== null)
					{
						delete activeAnimations[_managedName];
						_managedName = null;
					}

					if ((_state & 0x8800) !== 0x0000)
					{
						(_state & 0x0800) === 0x0800 && _events.dispatchEvent(new AnimationEvent("animationComplete"));
						(_state & 0x8000) === 0x8000 && _events.onComplete.apply(_events.onCompleteScope, _events.onCompleteParams);
					}
					if ((_state & 0x4400) !== 0x0000)
					{
						(_state & 0x0400) === 0x0400 && _events.dispatchEvent(new AnimationEvent("animationPause"));
						(_state & 0x8000) === 0x8000 && _events.onPause.apply(_events.onPauseScope, _events.onPauseParams);
					}
				}
			}
		}

		public function get propertyNames():Array
		{
			var names:Array = [];
			var count:uint = 0;

			for (var holder:UpdaterHolder = _updaterHolder; holder !== null; holder = holder.next)
			{
				for (var property:UpdaterProperty = holder.property; property !== null; property = property.next)
				{
					names[count++] = property.name;
				}
			}

			return names;
		}

		override public function start(name:String = null):IAnimation
		{
			if ((_state & 0x01) === 0x01)
			{
				return this;
			}

			_managedName = name;
			if (name !== null)
			{
				if (name in activeAnimations)
				{
					var anim:IAnimation = activeAnimations[name];
					anim.update();
					anim.pause();
				}
				activeAnimations[name] = this;
			}

			if ((_state & 0x08) === 0x00)
			{
				initialize();
			}

			_state |= 0x01;
			_state &= ~0x10000002;
			_manager.startTimer(this);

			if ((_state & 0x2200) !== 0x0000)
			{
				(_state & 0x0200) === 0x0200 && _events.dispatchEvent(new AnimationEvent("animationStart"));
				(_state & 0x2000) === 0x2000 && _events.onStart.apply(_events.onStartScope, _events.onStartParams);
			}

			return this;
		}

		public function to(name:String, end:Number, round:Boolean = false):IPropertyAnimation
		{
			return registerProperty(name, NaN, end, false, round);
		}

		public function toString():String
		{
			return "Animation(duration=" + _duration + ", position=" + _position + ", delay=" + _delay + ")";
		}

		override public function updateFromTimer(value:int):AnimationBase
		{
			if ((_state & 0x02) === 0x00)
			{
				var complete:int;
				value = _delta + value;
				if (value >= _duration)
				{
					if ((_state & 0x10) === 0x10)
					{
						_position = value - int(value / _duration) * _duration;
					}
					else
					{
						_position = _duration;
						complete = _state & 0x01;
					}
				}
				else if (value > -1)
				{
					_position = value;
				}
				else if (_position === -1)
				{
					return _next;
				}
				else
				{
					_position = -1;
				}

				var f:Number = _position <= _delay ? 0 : (_position - _delay) / (_duration - _delay);
				if (f > 0 || (f === 0 && (_state & 0x10000000) === 0x00000000))
				{
					f === 0 ? (_state |= 0x10000000) : (_state &= ~0x10000000);
					if ((_state & 0xff0000) !== 0x000000)
					{
						var p:uint;
						var t:uint = _state & 0xf00000;
						if (t === 0x100000)
						{
							f = _easing.ease(f);
						}
						else if (t === 0x200000)
						{
							p = _state & 0x0f0000;
							f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
						}
						else if (t === 0x300000)
						{
							p = _state & 0x0f0000;
							f = 1 - f;
							f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
							f = 1 - f;
						}
						else if (f < 0.5)
						{
							p = _state & 0x0f0000;
							f = f * 2;
							f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
							f = f * 0.5;
						}
						else
						{
							p = _state & 0x0f0000;
							f = f * -2 + 2;
							f = (p === 0x020000) ? (f * f) : (p === 0x030000) ? (f * f * f) : (p === 0x040000) ? (f * f * f * f) : (f * f * f * f * f);
							f = 1 - f * 0.5;
						}
					}

					if ((_state & 0x03000000) !== 0x00000000)
					{
						update0(f);
					}
					else
					{
						if (_updaterHolder.next === null)
						{
							_updaterHolder.updater.update(_element, _updaterHolder.property, _updaterHolder.prefix, f, _option, this);
						}
						else
						{
							for (var holder:UpdaterHolder = _updaterHolder; holder !== null; holder = holder.next)
							{
								holder.updater.update(_element, holder.property, holder.prefix, f, _option, this);
							}
						}
					}

					if ((_state & 0x1100) !== 0x0000)
					{
						(_state & 0x0100) === 0x0100 && _events.dispatchEvent(new AnimationEvent("animationUpdate"));
						(_state & 0x1000) === 0x1000 && _events.onUpdate.apply(_events.onUpdateScope, _events.onUpdateParams);
					}

					if (complete === 0x01)
					{
						_state &= ~0x01;
						_state |= 0x02;
						if (_managedName !== null)
						{
							delete activeAnimations[_managedName];
							_managedName = null;
						}

						if ((_state & 0x8800) !== 0x0000)
						{
							(_state & 0x0800) === 0x0800 && _events.dispatchEvent(new AnimationEvent("animationComplete"));
							(_state & 0x8000) === 0x8000 && _events.onComplete.apply(_events.onCompleteScope, _events.onCompleteParams);
						}
						if ((_state & 0x4400) !== 0x0000)
						{
							(_state & 0x0400) === 0x0400 && _events.dispatchEvent(new AnimationEvent("animationPause"));
							(_state & 0x8000) === 0x8000 && _events.onPause.apply(_events.onPauseScope, _events.onPauseParams);
						}
					}
				}
			}
			else
			{
				_next._previous = _previous;
				_previous._next = _next;
				--_manager._count;
				_state &= ~0x04;
			}

			return _next;
		}

		override protected function get animationName():String
		{
			return "Animation";
		}

		private function initialize0():void
		{
			try
			{
				for (var holder:UpdaterHolder = _updaterHolder; holder !== null; holder = holder.next)
				{
					holder.updater.initialize(_element, holder.property, holder.prefix, _option, this);
				}
			}
			catch (e:Error)
			{
				(_state & 0x01000000) === 0x01000000 && _events.dispatchEvent(new AnimationEvent("animationError"));
				(_state & 0x02000000) === 0x02000000 && _events.onError.apply(_events.onErrorScope, [e]);
			}
		}

		private function registerProperty(name:String, start:Number, end:Number, relative:Boolean, round:Boolean):IPropertyAnimation
		{
			_state &= ~0x08;

			var index:int = name.indexOf(":");
			var prefix:String;
			var holder:UpdaterHolder;
			var h:UpdaterHolder;

			if (index > -1)
			{
				prefix = name.substring(0, index);
				name = name.substring(index + 1);
			}

			if (prefix !== null)
			{
				for (h = _updaterHolder; h !== null; h = h.next)
				{
					if (h.prefix === prefix)
					{
						holder = h;
						break;
					}
				}
				if (holder === null)
				{
					var updater:IUpdater = _manager.getUpdaterByPrefix(prefix);
					if (updater !== null)
					{
						for (h = _updaterHolder; h !== null; h = h.next)
						{
							if (h.next === null)
							{
								h.next = holder = new UpdaterHolder(updater, prefix);
								break;
							}
						}
					}
				}
			}
			if (holder === null)
			{
				holder = _updaterHolder;
			}

			holder.property = new UpdaterProperty(name, start, end, relative, round, holder.property);
			return this;
		}

		private function update0(f:Number):void
		{
			try
			{
				if (_updaterHolder.next === null)
				{
					_updaterHolder.updater.update(_element, _updaterHolder.property, _updaterHolder.prefix, f, _option, this);
				}
				else
				{
					for (var holder:UpdaterHolder = _updaterHolder; holder !== null; holder = holder.next)
					{
						holder.updater.update(_element, holder.property, holder.prefix, f, _option, this);
					}
				}
			}
			catch (e:Error)
			{
				(_state & 0x01000000) === 0x01000000 && _events.dispatchEvent(new AnimationEvent("animationError"));
				(_state & 0x02000000) === 0x02000000 && _events.onError.apply(_events.onErrorScope, [e]);
			}
		}
		init();
	}
}

import sweezy.anim.core.IUpdater;
import sweezy.anim.updater.UpdaterProperty;

class UpdaterHolder
{

	public var next:UpdaterHolder;

	public var prefix:String;

	public var property:UpdaterProperty;

	public var updater:IUpdater;

	public function UpdaterHolder(updater:IUpdater, prefix:String)
	{
		this.updater = updater;
		this.prefix = prefix;
	}
}
